1   /*
2    * Copyright (C) 2007 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.collect;
18  
19  import com.google.common.annotations.GwtCompatible;
20  
21  import junit.framework.TestCase;
22  
23  import java.util.Iterator;
24  import java.util.NoSuchElementException;
25  
26  /**
27   * Unit test for {@code AbstractIterator}.
28   *
29   * @author Kevin Bourrillion
30   */
31  @SuppressWarnings("serial") // No serialization is used in this test
32  @GwtCompatible(emulated = true)
33  // TODO(cpovirk): why is this slow (>1m/test) under GWT when fully optimized?
34  public class AbstractIteratorTest extends TestCase {
35  
36    public void testDefaultBehaviorOfNextAndHasNext() {
37  
38      // This sample AbstractIterator returns 0 on the first call, 1 on the
39      // second, then signals that it's reached the end of the data
40      Iterator<Integer> iter = new AbstractIterator<Integer>() {
41        private int rep;
42        @Override public Integer computeNext() {
43          switch (rep++) {
44            case 0:
45              return 0;
46            case 1:
47              return 1;
48            case 2:
49              return endOfData();
50            default:
51              fail("Should not have been invoked again");
52              return null;
53          }
54        }
55      };
56  
57      assertTrue(iter.hasNext());
58      assertEquals(0, (int) iter.next());
59  
60      // verify idempotence of hasNext()
61      assertTrue(iter.hasNext());
62      assertTrue(iter.hasNext());
63      assertTrue(iter.hasNext());
64      assertEquals(1, (int) iter.next());
65  
66      assertFalse(iter.hasNext());
67  
68      // Make sure computeNext() doesn't get invoked again
69      assertFalse(iter.hasNext());
70  
71      try {
72        iter.next();
73        fail("no exception thrown");
74      } catch (NoSuchElementException expected) {
75      }
76    }
77  
78    public void testDefaultBehaviorOfPeek() {
79      /*
80       * This sample AbstractIterator returns 0 on the first call, 1 on the
81       * second, then signals that it's reached the end of the data
82       */
83      AbstractIterator<Integer> iter = new AbstractIterator<Integer>() {
84        private int rep;
85        @Override public Integer computeNext() {
86          switch (rep++) {
87            case 0:
88              return 0;
89            case 1:
90              return 1;
91            case 2:
92              return endOfData();
93            default:
94              fail("Should not have been invoked again");
95              return null;
96          }
97        }
98      };
99  
100     assertEquals(0, (int) iter.peek());
101     assertEquals(0, (int) iter.peek());
102     assertTrue(iter.hasNext());
103     assertEquals(0, (int) iter.peek());
104     assertEquals(0, (int) iter.next());
105 
106     assertEquals(1, (int) iter.peek());
107     assertEquals(1, (int) iter.next());
108 
109     try {
110       iter.peek();
111       fail("peek() should throw NoSuchElementException at end");
112     } catch (NoSuchElementException expected) {
113     }
114 
115     try {
116       iter.peek();
117       fail("peek() should continue to throw NoSuchElementException at end");
118     } catch (NoSuchElementException expected) {
119     }
120 
121     try {
122       iter.next();
123       fail("next() should throw NoSuchElementException as usual");
124     } catch (NoSuchElementException expected) {
125     }
126 
127     try {
128       iter.peek();
129       fail("peek() should still throw NoSuchElementException after next()");
130     } catch (NoSuchElementException expected) {
131     }
132   }
133 
134   public void testDefaultBehaviorOfPeekForEmptyIteration() {
135 
136     AbstractIterator<Integer> empty = new AbstractIterator<Integer>() {
137       private boolean alreadyCalledEndOfData;
138       @Override public Integer computeNext() {
139         if (alreadyCalledEndOfData) {
140           fail("Should not have been invoked again");
141         }
142         alreadyCalledEndOfData = true;
143         return endOfData();
144       }
145     };
146 
147     try {
148       empty.peek();
149       fail("peek() should throw NoSuchElementException at end");
150     } catch (NoSuchElementException expected) {
151     }
152 
153     try {
154       empty.peek();
155       fail("peek() should continue to throw NoSuchElementException at end");
156     } catch (NoSuchElementException expected) {
157     }
158   }
159 
160   public void testSneakyThrow() throws Exception {
161     Iterator<Integer> iter = new AbstractIterator<Integer>() {
162       boolean haveBeenCalled;
163       @Override public Integer computeNext() {
164         if (haveBeenCalled) {
165           fail("Should not have been called again");
166         } else {
167           haveBeenCalled = true;
168           sneakyThrow(new SomeCheckedException());
169         }
170         return null; // never reached
171       }
172     };
173 
174     // The first time, the sneakily-thrown exception comes out
175     try {
176       iter.hasNext();
177       fail("No exception thrown");
178     } catch (Exception e) {
179       if (!(e instanceof SomeCheckedException)) {
180         throw e;
181       }
182     }
183 
184     // But the second time, AbstractIterator itself throws an ISE
185     try {
186       iter.hasNext();
187       fail("No exception thrown");
188     } catch (IllegalStateException expected) {
189     }
190   }
191 
192   public void testException() {
193     final SomeUncheckedException exception = new SomeUncheckedException();
194     Iterator<Integer> iter = new AbstractIterator<Integer>() {
195       @Override public Integer computeNext() {
196         throw exception;
197       }
198     };
199 
200     // It should pass through untouched
201     try {
202       iter.hasNext();
203       fail("No exception thrown");
204     } catch (SomeUncheckedException e) {
205       assertSame(exception, e);
206     }
207   }
208 
209   public void testExceptionAfterEndOfData() {
210     Iterator<Integer> iter = new AbstractIterator<Integer>() {
211       @Override public Integer computeNext() {
212         endOfData();
213         throw new SomeUncheckedException();
214       }
215     };
216     try {
217       iter.hasNext();
218       fail("No exception thrown");
219     } catch (SomeUncheckedException expected) {
220     }
221   }
222 
223   public void testCantRemove() {
224     Iterator<Integer> iter = new AbstractIterator<Integer>() {
225       boolean haveBeenCalled;
226       @Override public Integer computeNext() {
227         if (haveBeenCalled) {
228           endOfData();
229         }
230         haveBeenCalled = true;
231         return 0;
232       }
233     };
234 
235     assertEquals(0, (int) iter.next());
236 
237     try {
238       iter.remove();
239       fail("No exception thrown");
240     } catch (UnsupportedOperationException expected) {
241     }
242   }
243 
244   public void testReentrantHasNext() {
245     Iterator<Integer> iter = new AbstractIterator<Integer>() {
246       @Override protected Integer computeNext() {
247         hasNext();
248         return null;
249       }
250     };
251     try {
252       iter.hasNext();
253       fail();
254     } catch (IllegalStateException expected) {
255     }
256   }
257 
258   // Technically we should test other reentrant scenarios (9 combinations of
259   // hasNext/next/peek), but we'll cop out for now, knowing that peek() and
260   // next() both start by invoking hasNext() anyway.
261 
262   /**
263    * Throws a undeclared checked exception.
264    */
265   private static void sneakyThrow(Throwable t) {
266     class SneakyThrower<T extends Throwable> {
267       @SuppressWarnings("unchecked") // not really safe, but that's the point
268       void throwIt(Throwable t) throws T {
269         throw (T) t;
270       }
271     }
272     new SneakyThrower<Error>().throwIt(t);
273   }
274 
275   private static class SomeCheckedException extends Exception {
276   }
277 
278   private static class SomeUncheckedException extends RuntimeException {
279   }
280 }
281